package com.we.wfc.timer.core;

import com.google.common.collect.Lists;
import com.we.wfc.common.utils.ConverterUtil;
import com.we.wfc.common.utils.OffsetDateTimeUtils;
import com.we.wfc.timer.entity.TimerJobOptions;
import com.we.wfc.timer.entity.TimerTrigger;
import com.we.wfc.timer.vo.TimerJobVo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Description: 调度任务管理器
 * @Author:Liangzy(Feeling)
 * @Date:Create in 2020/2/6 11:56 下午
 */
@Slf4j
@Component
@AllArgsConstructor
public class TimerManager {

    private final SchedulerFactoryBean clusteredScheduler;

    /**
     * 任务分组
     */
    public static String JOB_GROUP;
    /**
     * 触发器分组
     */
    public static String TRIGGER_GROUP;

    @Value("${we.timer.job:we_job}")
    public void setJobGroup(String jobGroup) {
        TimerManager.JOB_GROUP = jobGroup;
    }

    @Value("${we.timer.trigger:we_trigger}")
    public void setTriggerGroup(String triggerGroup) {
        TimerManager.TRIGGER_GROUP = triggerGroup;
    }

    /**
     * 删除任务(不能删除正在运行的任务，如果需要可以使用removeJob)
     *
     * @param jobName      任务名称
     * @param jobGroupName 任务分组
     */
    public void deleteJob(String jobName, String jobGroupName) {
        if (ConverterUtil.isNotEmpty(jobName, jobGroupName)) {
            Scheduler sched = getScheduler();
            JobKey jobKey = new JobKey(jobName, jobGroupName);
            try {
                sched.deleteJob(jobKey);
            } catch (SchedulerException e) {
                log.error("exception in QuartzManager.deleteJob", e);
            }
        }
    }

    /**
     * 移除一个正在运行的任务
     *
     * @param jobName      任务名称
     * @param jobGroupName 任务分组
     */
    public void removeJob(String jobName, String jobGroupName) {
        if (ConverterUtil.isNotEmpty(jobName, jobGroupName)) {
            Scheduler sched = getScheduler();
            JobKey jobKey = new JobKey(jobName, jobGroupName);
            try {
                List<? extends Trigger> triList = sched.getTriggersOfJob(jobKey);
                for (Trigger trigger : triList) {
                    TriggerKey triggerKey = trigger.getKey();
                    sched.pauseTrigger(triggerKey);
                    sched.unscheduleJob(triggerKey);
                }
                sched.deleteJob(jobKey);
            } catch (SchedulerException e) {
                log.error("exception in QuartzManager.deleteJob", e);
            }
        }
    }

    /**
     * 获取某组内全部的JobKey
     *
     * @param groupName
     * @return
     */
    public Set<JobKey> getJobKeysByGroupName(String groupName) {
        if (ConverterUtil.isEmpty(groupName)) {
            return null;
        }
        Scheduler sched = getScheduler();

        try {
            return sched.getJobKeys(GroupMatcher.groupEquals(groupName));
        } catch (SchedulerException e) {
            log.error("exception in QuartzManager.getJobKeysByGroupName", e);
            return null;
        }
    }

    /**
     * 使用jobName触发一次任务
     *
     * @param jobName      任务名称
     * @param jobGroupName 任务分组
     */
    public void triggerJob(String jobName, String jobGroupName, Map<String, Object> param) {
        if (ConverterUtil.isNotEmpty(jobName, jobGroupName)) {
            Scheduler sched = getScheduler();
            JobKey jobKey = new JobKey(jobName, jobGroupName);
            try {
                if (ConverterUtil.isNotEmpty(param)) {
                    JobDataMap jDataMap = new JobDataMap(param);
                    sched.triggerJob(jobKey, jDataMap);
                } else {
                    sched.triggerJob(jobKey);
                }
            } catch (SchedulerException e) {
                log.error("exception in QuartzManager.triggerJob", e);
            }
        }
    }

    /**
     * 使用jobName暂停任务
     *
     * @param jobName      任务名称
     * @param jobGroupName 任务分组
     */
    public void pauseJob(String jobName, String jobGroupName) {
        if (ConverterUtil.isNotEmpty(jobName, jobGroupName)) {
            Scheduler sched = getScheduler();
            JobKey jobKey = new JobKey(jobName, jobGroupName);
            try {
                sched.pauseJob(jobKey);
            } catch (SchedulerException e) {
                log.error("exception in QuartzManager.pauseJob", e);
            }
        }
    }

    /**
     * 使用jobName恢复任务
     *
     * @param jobName      任务名称
     * @param jobGroupName 任务分组
     */
    public void resumeJob(String jobName, String jobGroupName) {
        if (ConverterUtil.isNotEmpty(jobName, jobGroupName)) {
            Scheduler sched = getScheduler();
            JobKey jobKey = new JobKey(jobName, jobGroupName);
            try {
                sched.resumeJob(jobKey);
            } catch (SchedulerException e) {
                log.error("exception in QuartzManager.resumeJob", e);
            }
        }
    }

    /**
     * 校验job是否存在
     *
     * @param jobName
     * @param jobGroupName
     * @return
     * @throws SchedulerException
     */
    public boolean jobExists(String jobName, String jobGroupName) throws SchedulerException {
        if (ConverterUtil.isNotEmpty(jobName, jobGroupName)) {
            Scheduler sched = getScheduler();
            JobKey jobKey = new JobKey(jobName, jobGroupName);
            return sched.checkExists(jobKey);
        } else {
            throw new RuntimeException("Exception in QuartzManager.jobExists jobName,jobGroupName must be not null");
        }
    }

    /**
     * 校验触发器是否存在
     *
     * @param triggerName
     * @param triggerGroupName
     * @return
     * @throws SchedulerException
     */
    public boolean triggerExists(String triggerName, String triggerGroupName) throws SchedulerException {
        if (ConverterUtil.isNotEmpty(triggerName, triggerGroupName)) {
            Scheduler sched = getScheduler();
            TriggerKey tKey = new TriggerKey(triggerName, triggerGroupName);
            return sched.checkExists(tKey);
        } else {
            throw new RuntimeException("Exception in QuartzManager.triggerExists triggerName,triggerGroupName must be not null");
        }
    }

    /**
     * 新增任务
     *
     * @return -1:已存在 0:添加失败 1:添加成功
     */
    public int addJob(TimerJobOptions option) {
        try {
            // 校验cron表达式正确性
            if (ConverterUtil.isNotEmpty(option.getCron()) && !cronIsCorrect(option.getCron())) {
                log.error("add job forn cron:" + option.getCron() + " is wrong");
                return 0;
            }
            // 创建并调度任务
            if (!scheduleJob(option)) {
                return -1;
            }
            return 1;
        } catch (Exception e) {
            log.error("exception in QuartzManager.addJob", e);
            return 0;
        }
    }

    /**
     * 调度任务
     *
     * @param option Job的配置信息
     * @return
     * @throws SchedulerException
     * @throws ClassNotFoundException
     */
    public boolean scheduleJob(TimerJobOptions option) throws SchedulerException, ClassNotFoundException {
        Scheduler sched = getScheduler();
        // 是否已存在相同任务
        JobKey jKey = new JobKey(option.getJobName(), option.getJobGroupName());
        if (sched.checkExists(jKey)) {
            log.debug("add job:" + jKey.toString() + " is exists");
            removeJob(option.getJobName(), option.getJobGroupName());
        }
        // 创建job
        JobDetail job = createJobDetail(option, jKey);
        Trigger trigger = null;
        TriggerKey tKey = new TriggerKey(option.getTriggerName(), option.getTriggerGroupName());
        // 如果触发器相同则不需要创建新的，直接使用
        if (sched.checkExists(tKey)) {
            log.debug("add trigger:" + jKey.toString() + " is exists");
        }
        trigger = createTrigger(option, tKey);

        sched.scheduleJob(job, trigger);
        return true;
    }

    /**
     * 使用QuartzJobOptions创建一个Trigger
     *
     * @param option
     * @param triggerKey
     * @return
     */
    public Trigger createTrigger(TimerJobOptions option, TriggerKey triggerKey) {
        // 创建触发器
        TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
        triggerBuilder.withIdentity(triggerKey);
        // 是否立刻运行一次
        if (option.isStartNow()) {
            triggerBuilder.startNow();
        }
        // 设置描述
        if (ConverterUtil.isNotEmpty(option.getTriggerDescription())) {
            triggerBuilder.withDescription(option.getTriggerDescription());
        }
        // 设置优先级
        if (ConverterUtil.isNotEmpty(option.getPriority())) {
            triggerBuilder.withPriority(option.getPriority());
        }
        // 设置开始时间
        if (ConverterUtil.isNotEmpty(option.getStartTime())) {
            triggerBuilder.startAt(option.getStartTime());
        }
        // 设置结束时间
        if (ConverterUtil.isNotEmpty(option.getEndTime())) {
            triggerBuilder.startAt(option.getEndTime());
        }

        // 设置cron表达式(仅CronJob有效)
        if (ConverterUtil.isNotEmpty(option.getCron())) {
            // 使用cron的调度器
            triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(option.getCron()));
        } else {
            // 使用Simple的调度器
            SimpleScheduleBuilder sf = SimpleScheduleBuilder.simpleSchedule();

            // 设置重复时间(单位:秒)(仅SimpleJob有效)
            Integer intervalInSeconds = option.getIntervalInSeconds();
            if (ConverterUtil.isNotEmpty(intervalInSeconds) && intervalInSeconds.compareTo(0) > 0) {
                sf.withIntervalInSeconds(intervalInSeconds);
            }

            // 设置重复次数 小于0:无限次 大于1:重复执行N次(仅SimpleJob有效)
            Integer repeatCount = option.getRepeatCount();
            if (ConverterUtil.isNotEmpty(repeatCount) && repeatCount.compareTo(0) < 0) {
                sf.repeatForever();
            } else if (ConverterUtil.isNotEmpty(repeatCount) && repeatCount.compareTo(0) > 0) {
                sf.withRepeatCount(repeatCount);

            }
            // 设置重复时间(单位:秒)(仅SimpleJob有效)
            if (ConverterUtil.isEmpty(option.getCron())) {
                triggerBuilder.withSchedule(sf);
            }
        }

        return triggerBuilder.build();

    }

    /**
     * 使用QuartzJobOptions创建一个JobDetial
     *
     * @param option
     * @param jobKey
     * @return
     * @throws ClassNotFoundException
     */
    @SuppressWarnings("unchecked")
    public JobDetail createJobDetail(TimerJobOptions option, JobKey jobKey) throws ClassNotFoundException {
        // 创建jobBuilder
        Class<?> jobClazz = Class.forName(option.getJobClass());
        JobBuilder jobBuilder = JobBuilder.newJob((Class<? extends Job>) jobClazz);
        JobBuilder.newJob();
        jobBuilder.withIdentity(jobKey);
        // 描述不为空
        if (ConverterUtil.isNotEmpty(option.getDescription())) {
            jobBuilder.withDescription(option.getDescription());
        }
        // 参数不为空则设置参数
        if (ConverterUtil.isNotEmpty(option.getParam())) {
            jobBuilder.setJobData(new JobDataMap(option.getParam()));
        }
        // 是否持久化
        jobBuilder.storeDurably(option.isDurability());

        // 创建job
        return jobBuilder.build();
    }

    /**
     * 获取Scheduler
     *
     * @return
     */
    public Scheduler getScheduler() {
        if (ConverterUtil.isEmpty(clusteredScheduler)) {
            throw new RuntimeException("SchedulerFactoryBean is undefined");
        }
        try {
            return clusteredScheduler.getScheduler();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 判断是否有Quartz的工程实例,如果返回false，证明没有quartz的配置，不能进行quartz的相关操作
     *
     * @return
     */
    public boolean hasQuartzScheduler() {
        if (ConverterUtil.isEmpty(clusteredScheduler)) {
            return false;
        }
        return true;
    }

    /**
     * 获取某个调度器下全部的任务
     *
     * @return
     */
    public List<TimerJobVo> getJobDetailList() {
        List<TimerJobVo> result = Lists.newArrayList();
        Scheduler sched = getScheduler();
        try {
            // 获取全部的job分组
            List<String> jobGroupNames = sched.getJobGroupNames();
            for (String groupName : jobGroupNames) {
                Set<JobKey> jobKeys = sched.getJobKeys(GroupMatcher.<JobKey>groupEquals(groupName));
                for (JobKey jobKey : jobKeys) {
                    TimerJobVo vo = new TimerJobVo();
                    // 设置job
                    JobDetail jobDetail = sched.getJobDetail(jobKey);
                    vo.setGroup(jobKey.getGroup());
                    vo.setName(jobKey.getName());
                    vo.setClazz(jobDetail.getJobClass().getName());
                    vo.setDescription(jobDetail.getDescription());
                    vo.setDurable(jobDetail.isDurable());
                    vo.setPersistJobDataAfterExecution(jobDetail.isPersistJobDataAfterExecution());
                    vo.setRequestsRecovery(jobDetail.requestsRecovery());
                    vo.setConcurrentExectionDisallowed(jobDetail.isConcurrentExectionDisallowed());
                    vo.setDataMap(jobDetail.getJobDataMap());
                    // 设置触发器
                    List<? extends Trigger> triggerList = sched.getTriggersOfJob(jobKey);
                    List<TimerTrigger> tempList = Lists.newArrayList();
                    for (Trigger tri : triggerList) {
                        TimerTrigger qt = new TimerTrigger();
                        qt.setGroup(tri.getKey().getGroup());
                        qt.setName(tri.getKey().getName());
                        qt.setDescription(tri.getDescription());
                        Date nft = tri.getNextFireTime();
                        if (null != nft) {
                            qt.setNextFireTime(OffsetDateTimeUtils.longToOffsetDateTime(nft.getTime()));
                        }
                        Date pft = tri.getPreviousFireTime();
                        if (null != pft) {
                            qt.setPreFireTime(OffsetDateTimeUtils.longToOffsetDateTime(pft.getTime()));
                        }
                        Date st = tri.getStartTime();
                        if (null != st) {
                            qt.setStartTime(OffsetDateTimeUtils.longToOffsetDateTime(st.getTime()));
                        }
                        Date et = tri.getEndTime();
                        if (null != et) {
                            qt.setEndTime(OffsetDateTimeUtils.longToOffsetDateTime(et.getTime()));
                        }
                        qt.setPriority(tri.getPriority());
                        qt.setState(sched.getTriggerState(tri.getKey()).name());
                        tempList.add(qt);
                    }
                    vo.setTriggerList(tempList);
                    result.add(vo);
                }
            }
        } catch (SchedulerException e) {
            log.error("QuartzManager.getJobDetailList Scheduler = {} exception {}", sched, e);
        }
        return result;
    }

    /**
     * 判断cron表达式是否正确
     *
     * @param cron
     * @return
     */
    public static boolean cronIsCorrect(String cron) {
        try {
            CronExpression exp = new CronExpression(cron);
            exp.getNextValidTimeAfter(new Date());
            return true;
        } catch (ParseException e) {
            return false;
        }
    }
}
